#include "allochierarchy.h"
#include "meshcontainer.h"
#include "hierarchyframe.h"
#include "../core/defines.h"
#include "../core/resources/resourcemanager.h"
#include "../core/resources/resourcetexture.h"

using namespace gunsmith;

/*!	\brief		Method to allocate D3DXFRAMEs for the mesh hierarchy. (see remarks)
	\param		Name	Name of the frame.
	\param		ppFrame	[out] Receives the created frame.
	\return		HRESULT	Returns D3D_OK on success.
	\remarks	Allocates name for the D3DFRAME name and also initializes transformation and combined transformation matrix.
	\sa			*/
HRESULT CAllocHierarchy::CreateFrame(LPCSTR Name, LPD3DXFRAME *ppFrame)
{
	// create new frame
	THierarchyFrame *pFrame = new THierarchyFrame;
	ZeroMemory(pFrame, sizeof(THierarchyFrame));
	
	// allocate memory for frame name
	CopyName(&pFrame->Name, Name);

	// init matrices
	D3DXMatrixIdentity(&pFrame->TransformationMatrix);
	D3DXMatrixIdentity(&pFrame->CombinedTransformationMatrix);

	pFrame->pMeshContainer = NULL;
	pFrame->pFrameSibling = NULL;
	pFrame->pFrameFirstChild = NULL;
	*ppFrame = pFrame;

	return D3D_OK;
}

/*!	\brief		Creates the mesh container which holds relevant data for rendering output.
	\param		Name				Mesh name.
	\param		pMeshData			Mesh data.
	\param		pMaterials			Mesh material pointer.
	\param		pEffectInstances	Mesh effect pointer.
	\param		NumMaterials		Number of materials.
	\param		pAdjacency			Adjacency data array.
	\param		pSkinInfo			Skin info containing bone info.
	\param		ppNewMeshContainer	[out] Receives the created mesh container.
	\return		HRESULT
	\remarks	
	\sa			*/
HRESULT CAllocHierarchy::CreateMeshContainer(LPCSTR Name, CONST D3DXMESHDATA *pMeshData, CONST D3DXMATERIAL *pMaterials,
											CONST D3DXEFFECTINSTANCE *pEffectInstances, DWORD NumMaterials, CONST DWORD *pAdjacency,
											LPD3DXSKININFO pSkinInfo, LPD3DXMESHCONTAINER *ppNewMeshContainer)
{
	// if it's no standard mesh return
	if(pMeshData->Type != D3DXMESHTYPE_MESH)
		return E_FAIL;

	// init mesh container
	TMeshContainer *pMeshContainer = new TMeshContainer;
	ZeroMemory(pMeshContainer, sizeof(TMeshContainer));
	
	// init MeshData (though it's never used)
	pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;
	pMeshContainer->MeshData.pMesh = NULL;
	pMeshContainer->MeshData.pPMesh = NULL;
	pMeshContainer->MeshData.pPatchMesh = NULL;

	// mesh data
	pMeshContainer->pSkinnedMesh = NULL;
	pMeshContainer->pOrigMesh = pMeshData->pMesh;
	pMeshData->pMesh->AddRef();

	// copy name and set num materials
	CopyName(&pMeshContainer->Name, Name);
	pMeshContainer->NumMaterials = NumMaterials;

	// assign remainging values
	pMeshContainer->pEffects = NULL;
	pMeshContainer->pMaterials = NULL;
	pMeshContainer->pNextMeshContainer = NULL;

	// pass materials and container
	CreateTextures(pMaterials, pMeshContainer);

	// get number of faces
	pMeshContainer->iNumFaces = pMeshContainer->pOrigMesh->GetNumFaces();

	// skinning stuff
	pMeshContainer->pSkinInfo = pSkinInfo;

	if(pSkinInfo)
	{
		pSkinInfo->AddRef();

		// fill counters
		pMeshContainer->iNumBones = pMeshContainer->pSkinInfo->GetNumBones();

		// copy adjacency data
		pMeshContainer->pAdjacency = new DWORD[pMeshContainer->iNumFaces * 3];
		memcpy(pMeshContainer->pAdjacency, pAdjacency, sizeof(DWORD) * pMeshContainer->iNumFaces * 3);

		// create matrices
		pMeshContainer->ppCombinedMatrices = new D3DXMATRIX*[pMeshContainer->iNumBones];
		pMeshContainer->pBoneOffsetMatrices = new D3DXMATRIX[pMeshContainer->iNumBones];
		pMeshContainer->pBoneMatrices = new D3DXMATRIX[pMeshContainer->iNumBones];

		// get bone offset matrices
		for(DWORD i = 0; i < pMeshContainer->iNumBones; ++i)
			pMeshContainer->pBoneOffsetMatrices[i] = *pMeshContainer->pSkinInfo->GetBoneOffsetMatrix(i);

		pMeshContainer->iNumPalettes = min(26, pMeshContainer->iNumBones);

		// convert to blended mesh and optimize in place
		ConvertToIndexedBlendedMesh(pMeshContainer);
		pMeshContainer->pSkinnedMesh->OptimizeInplace(D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_VERTEXCACHE, pMeshContainer->pAdjacency, NULL, NULL, NULL);
		GetAABoundingBox(pMeshContainer);
	}
	else
	{
		pMeshContainer->iNumBones = 0;
		pMeshContainer->ppCombinedMatrices = NULL;
		pMeshContainer->pBoneOffsetMatrices = NULL;
		pMeshContainer->pBoneMatrices = NULL;
	}

	*ppNewMeshContainer = pMeshContainer;	// output container
	
	return D3D_OK;
}

/*!	\brief		Destroys (deallocates) all memory used by the frame.
	\param		pFrameToFree		Frame to destroy.
	\return		HRESULT __stdcall
	\remarks	
	\sa			*/
HRESULT __stdcall CAllocHierarchy::DestroyFrame(LPD3DXFRAME pFrameToFree)
{
	delete [] pFrameToFree->Name;
	delete pFrameToFree;
	return D3D_OK;
}

/*!	\brief		Destroys (deallocates) all memory used by the mesh container.
	\param		pMeshContainerToFree	Mesh container to destroy.
	\return		HRESULT __stdcall
	\remarks	
	\sa			*/
HRESULT __stdcall CAllocHierarchy::DestroyMeshContainer(LPD3DXMESHCONTAINER pMeshContainerToFree)
{
	TMeshContainer *pMeshContainer = (TMeshContainer*)pMeshContainerToFree;

	// release dx interfaces
	RELEASE(pMeshContainer->pOrigMesh);
	RELEASE(pMeshContainer->pSkinnedMesh);
	RELEASE(pMeshContainer->pSkinInfo);
	RELEASE(pMeshContainer->pVBAabb);
	RELEASE(pMeshContainer->pIBAabb);
	RELEASE(pMeshContainer->pVDAabb);

	// release textures
	for(DWORD i = 0; i < pMeshContainer->NumMaterials; ++i)
		RELEASE(pMeshContainer->pMaterials[i].pTexture);

	// delete allocated space
	delete [] pMeshContainer->Name;
	delete [] pMeshContainer->pAdjacency;
	delete [] pMeshContainer->ppCombinedMatrices;
	delete [] pMeshContainer->pBoneOffsetMatrices;
	delete [] pMeshContainer->pBoneMatrices;
	delete [] pMeshContainer->pMaterials;
	delete pMeshContainer;

	return D3D_OK;
}

/*!	\brief		Converts the mesh to an indexed blended mesh which we use for animation.
	\param		pMeshContainer	Mesh container to convert.
	\return		void
	\remarks	
	\sa			*/
void CAllocHierarchy::ConvertToIndexedBlendedMesh(TMeshContainer *pMeshContainer)
{
	pMeshContainer->pSkinInfo->ConvertToIndexedBlendedMesh(pMeshContainer->pOrigMesh,
															0,
															pMeshContainer->iNumPalettes,
															pMeshContainer->pAdjacency,
															NULL, NULL, NULL,
															&pMeshContainer->iMaxInfluences,
															&pMeshContainer->iNumAttrGroups,
															&pMeshContainer->pBoneCombBuf,
															&pMeshContainer->pSkinnedMesh);															
}

/*!	\brief		Copies the name string by allocating enoug data to store it.
	\param		ppName	[out] Pointer to a string receiving the copy.
	\param		pName	The actual name to copy.
	\return		void
	\remarks	
	\sa			*/
void CAllocHierarchy::CopyName(char **ppName, const char *pName)
{
	if(!pName)
	{
		*ppName = NULL;
		return;
	}

	size_t iSize = strlen(pName) + 1;
	char *pAlloc = new char[iSize];
	memcpy(pAlloc, pName, iSize*sizeof(char));
	*ppName = pAlloc;
}

/*!	\brief		Creates textures according to NumMaterials.
	\param		pMaterial		Material pointer.
	\param		pMeshContainer	Mesh container pointer.
	\return		void
	\remarks	
	\sa			*/
void CAllocHierarchy::CreateTextures(const D3DXMATERIAL *pMaterial, D3DXMESHCONTAINER *pMeshContainer)
{
	DWORD NumMaterials = pMeshContainer->NumMaterials;

	TMeshContainer *pContainer = (TMeshContainer*)pMeshContainer;
	pContainer->pMaterials = new TMaterial[NumMaterials];

	// work through the materials
	for(UINT i = 0; i < NumMaterials; ++i)
	{
		pContainer->pMaterials[i].MatD3D = pMaterial[i].MatD3D;

		// create texture
		if(pMaterial[i].pTextureFilename)
		{
			CResourceManager *pMngr = CResourceManager::GetInstance();
			CResourceTexture *pResTex = pMngr->CreateResource<CResourceTexture>(pMaterial[i].pTextureFilename, pMaterial[i].pTextureFilename);
			pMngr->Release();
			pContainer->pMaterials[i].pTexture = pResTex ? pResTex->GetTexture() : NULL;
		}
	}	
}

void CAllocHierarchy::GetAABoundingBox(TMeshContainer *pMeshContainer)
{
	void *pData = NULL;

	IDirect3DDevice9 *pDevice = NULL;
	pMeshContainer->pSkinnedMesh->GetDevice(&pDevice);

	if(SUCCEEDED(pMeshContainer->pSkinnedMesh->LockVertexBuffer(D3DLOCK_READONLY, &pData)))
	{
		D3DVERTEXELEMENT9 pVtxElements[MAX_FVF_DECL_SIZE];
		pMeshContainer->pSkinnedMesh->GetDeclaration(pVtxElements);
		
		DWORD iOffset = 0;
		for(int i = 0; pVtxElements[i].Usage != D3DDECLUSAGE_POSITION; ++i)
			iOffset = pVtxElements[i].Offset;

		DWORD iNumVertices = pMeshContainer->pSkinnedMesh->GetNumVertices();
		DWORD iStride = pMeshContainer->pSkinnedMesh->GetNumBytesPerVertex();
		D3DXComputeBoundingBox((D3DXVECTOR3*)pData, iNumVertices, iStride, &pMeshContainer->vAabbMin, &pMeshContainer->vAabbMax);

		pMeshContainer->pSkinnedMesh->UnlockVertexBuffer();
	}

	// begin construction of vertices of the bounding box
	const D3DXVECTOR3& vMin = pMeshContainer->vAabbMin;
	const D3DXVECTOR3& vMax = pMeshContainer->vAabbMax;

	pMeshContainer->pVertices[0].pos = D3DXVECTOR3(vMin.x, vMin.y, vMin.z);	// (0) 0 0 0
	pMeshContainer->pVertices[1].pos = D3DXVECTOR3(vMax.x, vMin.y, vMin.z);	// (1) 1 0 0
	pMeshContainer->pVertices[2].pos = D3DXVECTOR3(vMin.x, vMax.y, vMin.z);	// (2) 0 1 0
	pMeshContainer->pVertices[3].pos = D3DXVECTOR3(vMax.x, vMax.y, vMin.z);	// (3) 1 1 0

	pMeshContainer->pVertices[4].pos = D3DXVECTOR3(vMin.x, vMin.y, vMax.z);	// (4) 0 0 1
	pMeshContainer->pVertices[5].pos = D3DXVECTOR3(vMax.x, vMin.y, vMax.z);	// (5) 1 0 1
	pMeshContainer->pVertices[6].pos = D3DXVECTOR3(vMin.x, vMax.y, vMax.z);	// (6) 0 1 1
	pMeshContainer->pVertices[7].pos = D3DXVECTOR3(vMax.x, vMax.y, vMax.z);	// (7) 1 1 1

	for(int i=0; i < 8; i++)
		pMeshContainer->pVertices[i].col = 0xffffffff;

	// create vertex buffer
	pDevice->CreateVertexBuffer(8*sizeof(TMyVertex), 0, 0, D3DPOOL_DEFAULT, &pMeshContainer->pVBAabb, NULL);
	
	pMeshContainer->pVBAabb->Lock(0, 0, &pData, 0);
	memcpy(pData, (void*)pMeshContainer->pVertices, 8*sizeof(TMyVertex));
	pMeshContainer->pVBAabb->Unlock();

	UINT iIndices[24] = {0, 1, 1, 3, 2, 3, 0, 2,
						4, 5, 5, 7, 6, 7, 4, 6,
						0, 4, 1, 5, 2, 6, 3, 7};

	memcpy(pMeshContainer->iIndices, (void*)iIndices, 24*sizeof(UINT));

	// create index buffer
	pDevice->CreateIndexBuffer(24*sizeof(UINT), 0, D3DFMT_INDEX32, D3DPOOL_DEFAULT, &pMeshContainer->pIBAabb, NULL);
	
	pMeshContainer->pIBAabb->Lock(0, 0, &pData, 0);
	memcpy(pData, (void*)pMeshContainer->iIndices, 24*sizeof(UINT));
	pMeshContainer->pIBAabb->Unlock();


	D3DVERTEXELEMENT9 pVertexElements[] = {
		0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0,
		0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0,
		D3DDECL_END()
	};

	// create vertex declaration
	pDevice->CreateVertexDeclaration(pVertexElements, &pMeshContainer->pVDAabb);
	pMeshContainer->iStrideAabb = D3DXGetDeclVertexSize(pVertexElements, 0);

	pDevice->Release();
}